import axios from 'axios';
import type * as Axios from 'axios';
import { ElMessage } from 'element-plus';
import Base64 from 'crypto-js/enc-base64';
import MD5 from 'crypto-js/md5';
import _ from 'lodash';

import { i18n } from '@core/i18n';
import * as Enums from '@core/engine/Enums';
import ThirdPlatformSession from '@core/engine/ThirdPlatform/Session';
import Session from '@core/engine/Session';
import HistoryHelper from '@core/utils/HistoryHelper';
import router from './router';
import EnvConfig from './EnvConfig';

const baseConfig = {
  baseURL: EnvConfig.isNodeEnvDevelopment() ? 'http://local-cns.test.ctyun.cn:4000/api' : '/api',
  timeout: 30 * 1000,
  withCredentials: true,
};

const noAuthApis = [
  '/config',
  '/standalone/sso/sso/login',
];

const onRequest = async (request: Axios.AxiosRequestConfig) => {
  if (EnvConfig.isConnectToPublicPlatform()) {
    request.headers = {
      accountid: Session.getAccountId() || '',
      tenantId: Session.getAccountId() || '',
      ctuser: Session.getCtUserId() || '',
      regionid: Session.getRegionId() || '',
      userid: Session.getUserId() || '',
      ...request.headers,
    };
    return request;
  }
  if (request.url && noAuthApis.includes(request.url)) {
    return request;
  }
  const requestCanceler = getRequestCanceler(request);
  const authToken = ThirdPlatformSession.getAuthToken();
  if (authToken === undefined) {
    jumpToLoginPage();
    requestCanceler();
    return Promise.reject(new Error('Auth token is empty'));
  }
  request.headers = {
    ...request.headers,
    token: authToken,
  };
  return request;
};

/**
 * 订单服务请求的统一处理要求见 @see https://eop.ctyun.cn/api-proxy/docs/crm/#461c613a04
 */
const onOrderServiceRequest = async (request: Axios.AxiosRequestConfig) => {
  const processingRequest = await onRequest(request);
  if (request.method === 'get') {
    // GET 请求的参数需要放到 header 里
    Object.keys(request.params).forEach((paramKey) => {
      const paramValue = request.params[paramKey];
      if (paramValue === undefined) {
        return;
      }
      appendHeaders(request, { [paramKey]: _.isObject(paramValue) ? JSON.stringify(paramValue) : paramValue });
      // appendHeaders(request, { [paramKey]: JSON.stringify(paramValue) });
    });
    request.params = undefined;
  } else {
    // POST 等其他请求的参数需要组织成 x-www-form-urlencoded 要求的 key - value 形式
    const params = new URLSearchParams();
    Object.keys(request.data).forEach((dataKey) => {
      params.append(dataKey, request.data[dataKey]);
    });
    processingRequest.data = params;
  }
  // 对请求参数进行 MD5 计算，发送到 proxy service 进行签名与加密
  let paramsStringToSignature = '';
  if (!!request.params || !!request.data) {
    paramsStringToSignature = Object.values(request.params || request.data).join('\n');
  }
  appendHeaders(request, {
    contentMD5: MD5(paramsStringToSignature).toString(Base64),
    platform: 3,
  });
  return processingRequest;
};

const onRejected = (error: any) => {
  if (!error.response) {
    return Promise.reject(error);
  }
  onResponseError(error.response, error.response.status, error.response.data?.message || error.response.data?.msg);
  return Promise.reject(error);
};

const onResponse = (response: Axios.AxiosResponse) => {
  onResponseToken(response);
  return response.data;
};

const onCtcssResponse = (response: Axios.AxiosResponse) => {
  const responseBodyCode = response.data?.code;
  if (responseBodyCode === undefined) {
    return response.data.data;
  }
  if (isBodyCodeError(responseBodyCode)) {
    onResponseError(response, responseBodyCode, response.data.message || response.data.msg);
    return Promise.reject(response);
  }
  onResponseToken(response);
  return response.data.data;
};

const onLogServiceResponse = (response: Axios.AxiosResponse) => {
  const responseBodyCode = response.data?.result_code;
  if (responseBodyCode === undefined) {
    return response.data;
  }
  if (isBodyCodeError(responseBodyCode)) {
    onResponseError(response, responseBodyCode, response.data.reason);
    return Promise.reject(response);
  }
  onResponseToken(response);
  return response.data;
};

const onOrderResponse = (response: Axios.AxiosResponse) => {
  const responseBodyCode = response.data?.statusCode;
  if (responseBodyCode === undefined) {
    return response.data.returnObj;
  }
  if (isBodyCodeError(responseBodyCode, ['800'])) {
    onResponseError(response, responseBodyCode, response.data.message);
    return Promise.reject(response);
  }
  onResponseToken(response);
  return response.data.returnObj;
};

const onResponseToken = (response: Axios.AxiosResponse) => {
  if (response.headers.token) {
    ThirdPlatformSession.setAuthToken(response.headers.token);
  }
};

const getRequestCanceler = (request: Axios.AxiosRequestConfig) => {
  let requestCanceler = () => { /* do nothing */ };
  request.cancelToken = new axios.CancelToken((canceler) => {
    requestCanceler = canceler;
  });
  return requestCanceler;
};

const onResponseError = (
  response: Axios.AxiosResponse,
  errorCode: string | number,
  errorMessage?: string,
) => {
  switch (errorCode.toString()) {
    case '503':
    case '502':
    case '500':
    case '404': {
      ElMessage.error(errorMessage || i18n.global.t('common.networkError'));
      break;
    }
    case '403':
      router.push({ name: '403' }); break;
    case '401':
      jumpToLoginPage(response); break;
    default:
      ElMessage.error(errorMessage || i18n.global.t('common.requestError')); break;
  }
};

const jumpToLoginPage = (response?: Axios.AxiosResponse) => {
  if (EnvConfig.isConnectTo3rdPlatform()) {
    HistoryHelper.replaceToLogin();
    return;
  }
  if (EnvConfig.isConnectToPublicPlatform() && response?.data?.redirectTo) {
    HistoryHelper.replace(response.data.redirectTo);
  }
};

const isBodyCodeError = (httpCode: string | number, successCodes = ['0', '200', '200 OK']): boolean => {
  if (!httpCode) {
    return false;
  }
  return !successCodes.includes(httpCode.toString());
};

const appendHeaders = (
  request: Axios.AxiosRequestConfig,
  newHeaders: Axios.AxiosRequestHeaders | undefined,
) => {
  request.headers = { ...request.headers, ...newHeaders };
};

/**
 * 连接 proxy service 自身提供的 api 的 axios 实例。
 * 比如 CAS Client 与 Config 服务
 */
const proxyService = axios.create({ ...baseConfig });
/**
 * 连接 proxy service 注册的订单服务 API axios 实例。该服务会对请求参数进行处理，以适配订单服务的要求：
 *
 * - Content-Type 设置为 'application/x-www-form-urlencoded'
 * - contentMD5 设置为业务参数信息摘要，要求见 https://eop.ctyun.cn/api-proxy/docs/crm/#461c613a04
 * - GET 请求
 *   - 通过 config.params 传入的业务参数，会放被到 header 中
 * - POST 请求
 *   - 通过 params 传入的业务参数，会被放到 body 中
 */
const orderService = axios.create(_.merge({ ...baseConfig }, {
  headers: {
    backend: Enums.ServiceTypes.ProxyAppOrder,
    'Content-Type': 'application/x-www-form-urlencoded',
  },
}));
/**
 * 连接 proxy service 注册的 ReconstructionCRM 服务 API axios 实例。
 */
const rCrmService = axios.create(_.merge({ ...baseConfig }, {
  headers: { backend: Enums.ServiceTypes.ProxyAppRCrm },
}));
/**
 * 连接 CTCSS 组内私有 SSO 服务的 axios 实例
 */
const ctcssSsoService = axios.create({ ...baseConfig });
/**
 * 连接 console 服务 EOP API 的 axios 实例
 */
const consoleService = axios.create(_.merge({ ...baseConfig }, {
  headers: { backend: Enums.ServiceTypes.Console },
}));
/**
 * 连接 日志服务 EOP API 的 axios 实例
 */
const logService = axios.create(_.merge({ ...baseConfig }, {
  headers: { backend: Enums.ServiceTypes.LogsAudit },
}));
/**
 * 连接 面向其他应用（比如云防火墙）提供服务的日志服务 EOP API axios 实例
 */
const logPublicService = axios.create(_.merge({ ...baseConfig }, {
  headers: { backend: Enums.ServiceTypes.LogsAudit },
}));
/**
 * 连接 云防火墙服务 EOP API 的 axios 实例
 */
const firewallService = axios.create(_.merge({ ...baseConfig }, {
  headers: { backend: Enums.ServiceTypes.Firewall },
}));

proxyService.interceptors.request.use(onRequest, onRejected);
proxyService.interceptors.response.use(onResponse, onRejected);

orderService.interceptors.request.use(onOrderServiceRequest, onRejected);
orderService.interceptors.response.use(onOrderResponse, onRejected);

rCrmService.interceptors.request.use(onRequest, onRejected);
rCrmService.interceptors.response.use(onResponse, onRejected);

ctcssSsoService.interceptors.request.use(onRequest, onRejected);
ctcssSsoService.interceptors.response.use(onCtcssResponse, onRejected);

consoleService.interceptors.request.use(onRequest, onRejected);
consoleService.interceptors.response.use(onResponse, onRejected);

logService.interceptors.request.use(onRequest, onRejected);
logService.interceptors.response.use(onCtcssResponse, onRejected);

logPublicService.interceptors.request.use(onRequest, onRejected);
logPublicService.interceptors.response.use(onLogServiceResponse, onRejected);

firewallService.interceptors.request.use(onRequest, onRejected);
firewallService.interceptors.response.use(onCtcssResponse, onRejected);

export {
  proxyService as proxyRequest,
  orderService as orderRequest,
  rCrmService as rCrmRequest,
  ctcssSsoService as ctcssSsoRequest,
  consoleService as consoleRequest,
  logService as logRequest,
  logPublicService as logPublicRequest,
  firewallService as firewallRequest,
};
